import os, sys

#import dbus
import itertools as it, operator as op, functools as ft

from glob import iglob, glob

from forest.registry import register_tool, get_tool
from forest.tool import Tool

from os.path import join, relpath

#bus = dbus.SystemBus()

class ServiceClass(object):

    _cache = dict()
    obj = None #hmm

    def __new__(cls, unit=None, id=None, path=None):
        cls.path = path
        if unit:
            id = unicode(unit[0])
        try:
            self = cls._cache[id]
        except KeyError:
            self = super(ServiceClass, cls).__new__(cls)
            self.id = id
            cls._cache[id] = self
        if unit:
            self.obj = unit[6]
            self.state = tuple(it.imap(unicode, unit[3:5]))
        return self

    def enable(self):
        pass

    def disable(self):
        pass

    def __repr__(self):
        return self.id+':'+self.path

    def __eq__(self, other):
        return hash(self) == hash(other)

    def __hash__(self):
        if self.id is None:
            raise ValueError
        return hash(self.id)

    #@property
    #def type(self):
    #    return unicode(bus.get_object('org.freedesktop.systemd1', self.obj).Get(
    #         'org.freedesktop.systemd1.Service', 'Type',
    #         dbus_interface='org.freedesktop.DBus.Properties' )
    #        )

class ServiceLink(object):

    def __init__(self, path):
        self.path = path
        self.eid = unicode(self.path).split('/', 4)[-1]
        self.id = self.eid.split('/', 1)[-1]
        self.masked = os.readlink(self.path) == '/dev/null'

    def __repr__(self):
        return 'ServiceLink(%s)' % repr(self.to_service())

    def __hash__(self):
        if self.eid is None:
            raise ValueError
        return hash(self.eid)

    def to_service(self):
        return ServiceClass(id=self.id, path=self.path)

import ConfigParser
import io

class Service(object):
    def __init__(self, path):
        self.path = path
        self.eid = unicode(self.path).split('/')[-1]
        self.id = self.eid.split('/', 1)[-1]
        self.search_root = '/'

    def __repr__(self):
        return "Service(%s)" % (self.id)

    def set_search_root(self, root):
        self.search_root = root

    def enable(self):
        data = open(self.path).read()
        config = ConfigParser.RawConfigParser(allow_no_value = True)
        config.readfp(io.BytesIO(data))
        try:
            wanted_by = config.get("Install", "WantedBy").strip().split(' ')

            for wanted in wanted_by:
                target_dir = join(self.search_root, 'etc', 'systemd', 'system', '{}.wants'.format(wanted))
                target = join(target_dir, self.id)
                get_tool('system.ln')(relpath(self.path, target_dir), target, no_exists=True)

        except ConfigParser.NoOptionError:
            raise Exception( "\tNot wanted by")
        except ConfigParser.NoSectionError:
            raise Exception( "\tNo install section")


    def disable(self):
        for link in self.get_links():
            get_tool('system.remove')(link.path)

    def get_links(self):
        servicefiledepsglobs = ('%s/etc/systemd/system/*/%s' % (self.search_root, self.id), '%s/lib/systemd/system/*/%s' % (self.search_root, self.id))
        servicefilesdeps = it.imap(iglob,  servicefiledepsglobs)
        svc_links_list = set(it.imap(ServiceLink, it.chain.from_iterable(servicefilesdeps)))
        svc_links_masked = set(it.ifilter(op.attrgetter('masked'), svc_links_list))
        svc_links_enabled = svc_links_list - svc_links_masked
        svc_enabled = set(it.imap(op.methodcaller('to_service'), svc_links_enabled))
        return svc_links_enabled

    def restart(self, service):
        if service.count('.'):
            service = service.split('.')[0]

        with get_tool('system.chroot')(self.search_root):
            get_tool('system.execute')(['service', service, 'restart'])

@register_tool('systemd.Systemd')
class Systemd(object):

    def __init__(self, search_root='/'):
        self.search_root = os.path.abspath(search_root)
        self.service_dict = {}
        self.reload()

    def new(self, name, definition):
        fname = '/etc/systemd/system/%s' % (name)
        fullpath = join(self.search_root, fname)
        get_tool('files.write')(fullpath, definition)
        return Service(fullpath)

    def get_service(self, name):
        '''
            can be called with a proper name.
            or, can be called just with a name. tries a .service file then.
        '''
        if name.count('.'):
            return self.service_dict.get(name)

        service_name = '.'.join([name, 'service'])
        return self.service_dict.get(service_name)




    def reload(self):
        ''' Load all services on the system into self.services, w the new root
        '''
        #servicefileglobs = ('/etc/systemd/system/*.service', '/lib/systemd/system/*.service')

        servicefileglobs = (
            join(self.search_root, 'etc', 'systemd', 'system', '*.service'),
            join(self.search_root, 'etc', 'systemd', 'system', '*.mount'),
            join(self.search_root, 'etc', 'systemd', 'system', '*.target'),
            join(self.search_root, 'lib', 'systemd', 'system', '*.service'),
            join(self.search_root, 'lib', 'systemd', 'system', '*.mount'),
            join(self.search_root, 'lib', 'systemd', 'system', '*.target'))
        servicefiles = it.imap( iglob,  servicefileglobs)

        services = set(it.imap(Service, it.chain.from_iterable(servicefiles)))

        self.service_dict = {}
        for service in services:
            self.service_dict[service.eid] = service
            service.set_search_root(self.search_root)









## MAKE SURE TO CHROOT BEFORE USING THESE COMMAND LEST YOU DESTROY YOUR SYSTEM
#
#from os.path import join, exists
#from glob import glob
#
#from forest.registry import register_tool, get_tool
##from forest.tool import Tool
#
#
#@register_tool('systemd.mask_service')
#def mask_service(name):
#    ''' puts a link to /dev/null in the /etc/systemd/system directory
#
#        ln -s /dev/null /etc/systemd/system/ntpd.service
#    '''
#    get_tool('system.ln')('/dev/null', '/etc/systemd/system/%s.service' % name)
#
@register_tool('systemd.sysvinit_service')
def sysvinit_service(name, level, prio = None, enable = True):
    '''creates the relevant links or renames existing, adjusting
    priority - what is shut down late must come up early etc.
    '''
    orig_prefix, dest_prefix = ('K', 'S') if enable else ('S', 'K')

    link_fmt = 'etc/rc.d/rc{level}.d/{0}{1}{name}'
    common_fmt = {'name' : name, 'level' : level}

    try:
        #print (link_fmt.format(orig_prefix, '*', **common_fmt))
        #print (glob.glob(link_fmt.format(orig_prefix, '*', **common_fmt)))
        orig_link = glob(link_fmt.format(orig_prefix, '*', **common_fmt))[0]
        #print (orig_link)
        orig_prio = int(orig_link.split(orig_prefix)[1].rstrip(name))
        dest_link = link_fmt.format(dest_prefix, 100 - orig_prio, **common_fmt)
        get_tool('system.move')(orig_link, dest_link)

    except (OSError, IndexError):
        if glob(link_fmt.format(dest_prefix, '*', **common_fmt)) != []:
            #print("that link already exists: {}".format(glob.glob(link_fmt.format(dest_prefix, '*', **common_fmt))))
            return
        if prio is None:
            #print("no service links in rc.d directory - specify priority to create one")
            return
        dest_link = link_fmt.format(dest_prefix, prio, **common_fmt)
        get_tool('system.ln')('../init.d/{}'.format(name), dest_link)

#@register_tool('systemd.Systemd')
#class Systemd(object):
#    def __init__(self, root='/'):
#        self.root = root
#
#    def disable(self, name, skip_check = False):
#        #w/o this, doesn't work after excluding .service files from a pkg
#        #but - a poorly populated chroot can fail to support polling
#        #eg complaining about "unknown encoding: string-escape" if
#        #some bins aren't accessible in the chroot..
#        #http://www.velocityreviews.com/forums/t753606-cant-use-subprocess-popen-after-os-chroot-why.html
#        if not skip_check and not exists('/lib/systemd/system/{}'.format(name)):
#            for _, broken in get_tool('system.spoll')(['find', '/etc/systemd/system/', '-name', name]):
#                get_tool('system.remove')(broken.strip())
#                return
#        get_tool('system.execute')(['/bin/systemctl', '--root', self.root, 'disable', name], expected=[0, 1, 2])
#
#    def enable(self, name):
#        get_tool('system.execute')(['/bin/systemctl', '--root', self.root, 'enable', name], expected=[0, 1, 2])
#
#    def start(self, name):
#        get_tool('system.execute')(['/bin/systemctl', '--root', self.root, 'start', name], expected=[0, 1, 2])
#
#    def mask(self, name):
#        get_tool('system.execute')(['/bin/systemctl', '--root', self.root, 'mask', name], expected=[0, 1, 2])
#
#    def new(self, name, definition):
#        ''' create service file and install into /etc/systemd/system/
#
#            Example definition:
#
#            [Unit]
#            Description=OpenSSH server daemon
#            After=syslog.target network.target auditd.service
#
#            [Service]
#            EnvironmentFile=/etc/sysconfig/sshd
#            ExecStart=/usr/sbin/sshd -D $OPTIONS
#            ExecReload=/bin/kill -HUP $MAINPID
#
#            [Install]
#            WantedBy=multi-user.target
#        '''
#
#        fname = '/etc/systemd/system/%s' % (name)
#        fullpath = join(self.root, fname)
#        get_tool('files.write')(fullpath, definition)
#
#@register_tool('systemd.mask_target')
#def mask_target(name):
#    ''' NOT SURE IF THIS WORKS
#    '''
#    get_tool('system.ln')('/dev/null', '/etc/systemd/system/%s.target' % name)
#
##@register_tool('systemd.disable')
##def disable(name):
##    ''' systemctl disable ntpd.service
##    '''
##    get_tool('system.execute')(['systemctl', 'disable', '%s.service' % name])
##
@register_tool('systemd.new_target')
def new_target(name, definition):
    ''' mkdir -p $(DESTDIR)/lib/systemd/system/anaconda.target.wants

        Example definition:

        [Unit]
        Description=Graphical Interface
        Requires=multi-user.target
        After=multi-user.target
        Conflicts=rescue.target
        AllowIsolate=yes

        [Install]
        Alias=default.target
    '''

    get_tool('files.write')('/etc/systemd/system/%s.target' % name, definition)

    get_tool('system.mkdir')('/etc/systemd/system/%s.target.wants' % name)

#
#@register_tool('systemd.set_target_wants')
#def set_target_wants(name, wants):
#    ''' Links the list of services in wants to the targets *.wants directory.
#
#        ln -sf /lib/systemd/system/anaconda-shell.service $(DESTDIR)/lib/systemd/system/anaconda.target.wants
#        ln -sf /lib/systemd/system/loader.service $(DESTDIR)/lib/systemd/system/anaconda.target.wants
#        +	ln -sf /lib/systemd/system/rsyslog.service $(DESTDIR)/lib/systemd/system/anaconda.target.wants
#        ln -sf /lib/systemd/system/udev-settle.service $(DESTDIR)/lib/systemd/system/anaconda.target.wants
#    '''
#
#    basedir = '/etc/systemd/system/'
#    target_wants_dir = join(basedir, '%s.target.wants' % name)
#
#    for wanted_service in wants:
#        basename = '%s.service' % wanted_service
#        src = join(basedir, basename)
#        get_tool('system.ln')(src, join(target_wants_dir, basename))

@register_tool('systemd.set_default_target')
def set_default_target(name):
    get_tool('system.ln')('/lib/systemd/system/%s.target' % name, '/etc/systemd/system/default.target', force = True)
